前端异常处理-ttc
前端异常分类
JS 语法错误、代码异常
AJAX 请求异常
静态资源加载异常
Promise 异常
Iframe 异常
跨域 Script error
崩溃和卡顿
异常处理
Try-Catch
无法捕捉到语法错误及异步错误,只能捕捉运行时错误;
可以拿到出错的信息,堆栈,出错的文件、行号、列号;
只能捕获块里面的错误;
window.onerror
注意:onerror 最好写在所有 JS 脚本的前面,否则有可能捕获不到错误;
onerror 无法捕获语法错误;
在实际的使用过程中,onerror 主要是来捕获预料之外的错误(包括全局错误)
而 try-catch 则是用来在可预见情况下监控特定的错误,两者结合使用更加高效
window.addEventListener
Promise Catch
window.addEventListener("unhandledrejection", function(e){})
VUE errorHandler
iframe 异常
Script error
崩溃和卡顿
Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker 一般情况下不会崩溃;
Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态;网页可以通过 navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW 发送消息。
错误上报
通过 Ajax 发送数据 因为 Ajax 请求本身也有可能会发生异常,而且有可能会引发跨域问题,一般情况下更推荐使用动态创建 img 标签的形式进行上报。
动态创建 img 标签的形式
收集异常信息量太多,怎么办?实际中,我们不得不考虑这样一种情况:如果你的网站访问量很大,那么一个必然的错误发送的信息就有很多条,这时候,我们需要设置采集率,从而减缓服务器的压力:
总结
可疑区域增加 Try-Catch
全局监控 JS 异常 window.onerror
全局监控静态资源异常 window.addEventListener
捕获没有 Catch 的 Promise 异常:unhandledrejection
VUE errorHandler
监控网页崩溃:window 对象的 load 和 beforeunload
跨域 crossOrigin 解决
前端异常监控
$_PS: 主要说了window.onerror。现代异常及上报无
如果说前端的异常监控有个救星的话,我想那就是 window.onerror
这个全局错误监听事件了。它给了我们统一处理前端全局错误的机会,使得错误上报有了一线生机。1
window.onerror = function(messageOrEvent, source, lineno, colno, error) { ... }
- message: 错误信息,在 HTML 中的
onerror
属性中设置的回调可以传递事件 - source: 出错文件的 url
- lineno: 出错位置的行数
- colno: 出错时的列数
- error: 出错时的 Error 对象。
实践中发现最后个参数 Error 对象中的值因浏览器的实现各有差异,比如 Chrome 中包含 message
和 stack
,而 Safari 中则包含了前面四个参数的所有值。这在下面的示例代码的结果中可以看得出来。
window.onerror
注意window.onerror
需要在有服务端的情况下才能正常工作,本地直接打开页面测试获取不到任何有用的错误信息。可以在命令行启动一个简单的服务端来进行测试。
因为 Mac 自带 Python,一般需要用到服务端的时候,我喜欢用 Python 自带的 SimpleHTTPServer
:1
2
3python -m SimpleHTTPServer
or
python3 -m http.server
以下代码我们对全局错误进行监听,然后将错误打印到页面:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20window.onerror = function (msg, source, line, col, error) {
printError.apply(null, arguments);
};
function printError(msg, source, line, col, error) {
var detail =
'msg:' +
msg +
'\ncourse:' +
source +
'\nline:' +
line +
'\ncol:' +
col +
'\nerror:' +
JSON.stringify(error, Object.getOwnPropertyNames(error));
var div = document.createElement('pre');
div.innerHTML = detail
document.body.appendChild(div);
}
然后在页面放上按钮以触发错误。这里测试了两种错误,一种运行时 JS 的抛错,另一种手动在代码中抛出的错误。
1 | <button onclick="excptionGenerate()">点我执行出错代码</button> |
浏览器兼容性
要知道,最初版本的全局错误监听事件是这样的:1
window.onerror = function(messageOrEvent, source, lineno) { ... }
后来才增加了 colno
和 error
。而后来加的这两个参数其实是非常有用的。
因为线上代码一般为压缩过的代码,所有内容都在一行,假如没有提供发生问题的列数,这样的错误日志要追查起来很不方便。
错误对象则直接提供了错误堆栈信息(通过 error.stack
访问),就像我们在浏览器控制台看到的一样,对于定位问题十分有帮助。
主流浏览器中, Chrome, Safari 已经完成了5个参数的支持。
Firefox 从 31 开始支持了完整的5个参数。
截止到目前, 微软的 Edge 浏览器还没有实现对新增两个参数的支持。其实现情况可以在这里查阅得到。
- 小贴士 *
过程中顺便发现了微软Edge这个API Catalog页面可以查到主流浏览器对名前端特性的实现情况,数据比 caniuse 全,譬如
window.onerror
在 caniuse 上则没有。
从这里也可以看到,其他主流浏览器都已经有了完整的支持。
IE,(逃~)
try catch
对于不提供第5个参数的环境,我们是拿不到错误堆栈信息的。这种情况下对错误的追查帮助不大。
但是,手动在代码中捕获并抛出的错误,是带了堆栈信息的。这就有了补救的希望。我们可以将可能出错的地方,或者我们期望进行监控的地方,使用 try catch。
1 | function tryCatchError() { |
同时在页面中添加按钮来调用新的测试函数。1
<button onclick="tryCatchError()">利用 try catch 捕获异常并打印错误堆栈</button>
我们看到,这种方式确实能得到详细的报错堆栈。
全局无法捕获的情况
除了考虑上面的浏览器兼容性问题外,还有其他一些情况,也是无法通过这个全局的 onerror 获取到详细报错信息的。
跨域情况的错误捕获(CDN)
window.onerror
有个限制,来自非同域的代码有报错,不会给出错误的详细信息,只能得到一个 Script error.
。这是浏览器出于安全考虑,不向第三方泄露信息而做的一个措施。但往往线上代码大部分都部署在 CDN,所以这个限制的影响还挺常见。
不过还好,某些浏览器中可以通过配置来更改这一行为,让我们能正常拿到报错的详细信息。
还有一点,就是虽然在 window.onerror
中倒不到详细的报错信息,但在浏览器控制台是可以看到详细信息的。
如果是跨域脚本,则提示去控制台查看报错信息1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19window.onerror = function (msg, url, lineNo, columnNo, error) {
var string = msg.toLowerCase();
var substring = "script error";
if (string.indexOf(substring) > -1){
alert('Script Error: See Browser Console for Detail');
} else {
var message = [
'Message: ' + msg,
'URL: ' + url,
'Line: ' + lineNo,
'Column: ' + columnNo,
'Error object: ' + JSON.stringify(error)
].join(' - ');
alert(message);
}
return false;
};
控制台能看到对于线上的错误监控来说没多大用,还是得解决上报的问题。我们来看看如何设置跨域脚本让我们可以捕获时拿到错误堆栈信息。
下面看跨域脚本的配置。
CDN 上开启允许跨域
1
2
3Access-Control-Allow-Origin:*
或者
Access-Control-Allow-Origin: domain of your site然后 script 标签上设置跨域标识为匿名
1
<script crossorigin="anonymous" src="//url/for/your/cdn/scripts"></script>
唯一需要注意的是,一旦在前端设置了 crossorigin
,要确保服务端相应设置了允许跨域的响应头,否则整个脚本文件会加载失败,影响页面正常功能。
目前来看,除了 Opera外,各主流浏览器都有支持此属性。
iframe 中异常的捕获
iframe 中发生异常,外界的 onerror
是不会触发的。但如果 iframe 地址同域,那么我们就可以设置 iframe 的全局 onerror
进行监听。
1 | document.getElementById("myiframe").contentWindow.onerror=function() { |
四. 数据上报
上报方式
用获取图片(gif)形式上报。
• 首先POST/GET会有跨域的问题,图片没有跨域问题
• IMG是比较老标签没有兼容问题
• 缺点就是一次上报的数据量有限一般不超过 2K,同时对页面载入性能可能有影响
上报时机
如果每次收集到数据就直接上报,请求数会比较大,对后台要求比较高。可以采用定时上报外加监听unload事件,当发生unload时将剩下的数据全部上报。
上报数据
数据可以结合业务需要进行过滤,截断,采样命中。然后进行压缩处理后再上报。
后记
后续可以开垦的点:
- 本地模拟跨域
- 自己实现异常上报的库
[10 种最常见的 Javascript 错误]
2018.4.15 星期日 11:30
JavaScript-错误-Top-10
为了便于阅读,我们将每个错误描述都缩短了。接下来,让我们深入到每一个错误,来确定什么会导致它,以及如何避免创建它。
1. Uncaught TypeError: Cannot read property
2. TypeError: ‘undefined’ is not an object
这是在 Safari 中读取属性或调用未定义对象上的方法时发生的错误。您可以在 Safari Developer Console 中轻松测试。这与 1 中提到的 Chrome 的错误基本相同,但 Safari 使用了不同的错误消息提示语。
3. TypeError: null is not an object
这是在 Safari 中读取属性或调用空对象上的方法时发生的错误。
4. (unknown): Script error
当未捕获的 JavaScript 错误(通过window.onerror处理程序引发的错误,而不是捕获在try-catch中)被浏览器的跨域策略限制时,会产生这类的脚本错误。 例如,如果您将您的 JavaScript 代码托管在 CDN 上,则任何未被捕获的错误将被报告为“脚本错误” 而不是包含有用的堆栈信息。这是一种浏览器安全措施,旨在防止跨域传递数据,否则将不允许进行通信。
要获得真正的错误消息,请执行以下操作:
发送 ‘Access-Control-Allow-Origin’ 头部
将 Access-Control-Allow-Origin 标头设置为 * 表示可以从任何域正确访问资源。 如有必要,您可以将域替换为您的域:例如,Access-Control-Allow-Origin:www.example.com。 但是,处理多个域会变得棘手,如果你使用 CDN,可能由此产生更多的缓存问题会让你感觉到这种努力并不值得。 在这里看到更多。
这里有一些关于如何在各种环境中设置这个头文件的例子:
Apache
在 JavaScript 文件所在的文件夹中,使用以下内容创建一个 .htaccess 文件:Header add Access-Control-Allow-Origin"*"
Nginx
将 add_header 指令添加到提供 JavaScript 文件的位置块中:location ~^/assets/{ add_header Access-Control-Allow-Origin*; }
HAProxy
将以下内容添加到您为 JavaScript 文件提供资源服务的后端:rspadd Access-Control-Allow-Origin:\ *
在
<script>
中设置 crossorigin=”anonymous”在您的 HTML 代码中,对于您设置了Access-Control-Allow-Origin header 的每个脚本,在 script 标签上设置crossorigin =“anonymous”。在脚本标记中添加 crossorigin 属性之前,请确保验证上述 header 正确发送。 在 Firefox 中,如果存在crossorigin属性,但Access-Control-Allow-Origin头不存在,则脚本将不会执行。
5. TypeError: Object doesn’t support property
这是您在调用未定义的方法时发生在 IE 中的错误。
这相当于 Chrome 中的 “TypeError:”undefined“ is not a function” 错误。 是的,对于相同的逻辑错误,不同的浏览器可能具有不同的错误消息。
对于使用 JavaScript 命名空间的 Web 应用程序,这是一个 IE l浏览器的常见的问题。 在这种情况下,99.9% 的原因是 IE 无法将当前名称空间内的方法绑定到 this 关键字。 例如:如果你 JS 中有一个命名空间 Rollbar 以及方法 isAwesome 。
6. TypeError: ‘undefined’ is not a function
当您调用未定义的函数时,这是 Chrome 中产生的错误。
7. Uncaught RangeError: Maximum call stack
这是 Chrome 在一些情况下会发生的错误。 一个是当你调用一个不终止的递归函数。您可以在 Chrome 开发者控制台中进行测试。
此外,如果您将值传递给超出范围的函数,也可能会发生这种情况。 许多函数只接受其输入值的特定范围的数字。 例如:Number.toExponential(digits) 和 Number.toFixed(digits) 接受 0 到 20 的数字,Number.toPrecision(digits)接受 1 到 21 的数字。
8. TypeError: Cannot read property ‘length’
这是 Chrome 中发生的错误,因为读取未定义变量的长度属性。
9. Uncaught TypeError: Cannot set property
当我们尝试访问一个未定义的变量时,它总是返回 undefined,我们不能获取或设置任何未定义的属性。
10. ReferenceError: event is not defined
当您尝试访问未定义的变量或超出当前范围的变量时,会引发此错误。
结论
我们希望你学到了新的东西,可以避免将来的错误,或者本指南帮助你解决了头痛的问题。
尽管如此,即使有最佳实践,生产中也会出现意想不到的错误。能够查看影响用户的错误,并拥有快速解决问题的好工具,这一点非常重要。推荐 Rollbar。
其它:
最后,为你推荐
<!– 【第870期】JavaScript错误处理和堆栈追踪
【第318期】如何修复那些奇怪的 JavaScript 错误
–>